package com.kalvan.admin.log;

import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.kalvan.admin.annotation.SysLog;
import com.kalvan.web.feign.client.SccLogClient;
import com.kalvan.web.feign.client.req.LogRequest;
import com.kalvan.client.constant.CommonContextCacheKey;
import com.kalvan.client.context.RequestContextHolder;
import com.kalvan.client.model.Request;
import com.kalvan.client.system.SystemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author chenliang
 */
@Aspect
@Component
@Slf4j
public class LogAopAction {
    /**
     * 操作前原始信息线程存储
     */
    private static final ThreadLocal<String> OLD_DETAIL_THREAD_LOCAL = new ThreadLocal<>();
    /**
     * 操作后信息线程存储
     */
    private static final ThreadLocal<String> NEW_DETAIL_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 操作后信息线程存储
     */
    private static final ThreadLocal<LogRequest> LOG_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 操作类型
     */
    private static final String LOG_GROUP = "group";
    /**
     * 操作类型
     */
    private static final String LOG_TYPE = "type";
    /**
     * 操作地址
     */
    private static final String LOG_ACTION = "action";
    /**
     * 操作说明
     */
    private static final String LOG_REMARK = "remark";
    /**
     * 被操作者抓取的参数
     */
    private static final String LOG_OPERATED = "operated";

    /**
     * 日志存储服务需要scc实现
     */
    @Autowired(required = false)
    private SccLogClient sccLogClient;

    @Pointcut("@annotation(com.kalvan.admin.annotation.SysLog)")
    private void pointCutMethod() {
    }

    /**
     * 记录操作日志
     *
     * @param joinPoint 切面
     */
    @After("pointCutMethod()")
    public void recordLog(JoinPoint joinPoint) {
        if (sccLogClient == null) {
            log.warn("scc服务未找到不启用操作日志");
            return;
        }
        LogRequest logObject = getLog();
        try {
            if (logObject == null) {
                logObject = initLogDto(joinPoint);
            }
            //统一设置下面的系统信息
            logObject.setLogUser(String.valueOf(RequestContextHolder.getContext().get(CommonContextCacheKey.USER_NAME)));
            logObject.setSystemCode(SystemUtil.constants.getName());
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()))
                    .getRequest();
            logObject.setLogIp(ServletUtil.getClientIP(request));
            logObject.setLogTime(DateUtil.date());
            Request<LogRequest> logRequest = new Request<>();
            logRequest.setData(logObject);
            sccLogClient.insertLog(logRequest);
        } catch (Exception e) {
            log.error("插入日志异常:{}" ,JSON.toJSONString(logObject), e);
        }
    }

    /**
     * 从方法注解中获取LogDto对象
     *
     * @param joinPoint 切面
     * @return LogRequest
     */
    private LogRequest initLogDto(JoinPoint joinPoint) throws Exception {
        LogRequest logRequest = new LogRequest();
        Map<String, String> params = showParams();
        // 下面开始获取 Action Type Remark
        Map<String, Object> map = getLogMark(joinPoint);
        logRequest.setLogGroup(map.get(LOG_GROUP).toString());
        logRequest.setLogType((Integer) map.get(LOG_TYPE));
        logRequest.setLogAction(map.get(LOG_ACTION).toString());
        logRequest.setLogRemark(map.get(LOG_REMARK).toString());
        // 获取操作明细
        if (StringUtils.isNotBlank(map.get(LOG_OPERATED).toString())) {
            logRequest.setLogOperated(params.get(String.valueOf(map.get(LOG_OPERATED))));
        }
        logRequest.setLogOldDetail(getOldDetail());
        logRequest.setLogDetail(getNewDetail());
        return logRequest;
    }

    /**
     * 获取注解信息
     *
     * @param joinPoint 切面
     * @return 日志标签信息
     */
    private Map<String, Object> getLogMark(JoinPoint joinPoint) throws Exception {
        Map<String, Object> map = new HashMap<>(5);
        // 目标类全路径名
        String targetName = joinPoint.getTarget().getClass().getName();
        // 反射得到目标类
        Class<?> targetClass = Class.forName(targetName);
        // 目标方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 目标方法名（正在访问的方法）
        String methodName = methodSignature.getName();
        //方法上的注解内容提取
        SysLog sysLog = method.getAnnotation(SysLog.class);
        if (sysLog != null) {
            map.put(LOG_GROUP, sysLog.group());
            map.put(LOG_TYPE, sysLog.type().getCode());
            map.put(LOG_ACTION, sysLog.action());
            map.put(LOG_REMARK, sysLog.remark());
///          map.put(LOG_PARAMS, logAnnotation.params());
            map.put(LOG_OPERATED, sysLog.operated());
        } else {
            throw new Exception("日志注解获取异常" + methodName);
        }
        //从request里获取
        if (map.get(LOG_ACTION) == null || StringUtils.isBlank(map.get(LOG_ACTION).toString())) {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()))
                    .getRequest();
            map.put(LOG_ACTION, request.getRequestURI());
        }
        // 获取类上的日志类型注解
        SysLog logGroup = targetClass.getAnnotation(SysLog.class);
        if (logGroup != null) {
            if (map.get(LOG_GROUP) == null || StringUtils.isBlank(map.get(LOG_GROUP).toString())) {
                map.put(LOG_GROUP, logGroup.group());
            }
        }
        // 检查是否正确使用
        if (map.get(LOG_GROUP) == null || StringUtils.isBlank(map.get(LOG_GROUP).toString())) {
            throw new Exception("日志组不能为空");
        }
        return map;
    }

    /**
     * 获取请求参数
     *
     * @return 请求参数
     */
    private Map<String, String> showParams() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()))
                .getRequest();
        Map<String, String> map = new HashMap<>(5);
        Enumeration<?> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            String[] paramValues = request.getParameterValues(paramName);
            if (paramValues.length == 1) {
                String paramValue = paramValues[0];
                if (paramValue.length() != 0) {
                    map.put(paramName, paramValue);
                }
            }
        }
        return map;
    }


    /**
     * 更新前数据
     */
    private String getOldDetail() {
        try {
            String oldDetail = OLD_DETAIL_THREAD_LOCAL.get();
            if (StringUtils.isNotBlank(oldDetail)) {
                OLD_DETAIL_THREAD_LOCAL.remove();
                return oldDetail;
            }
        } catch (Throwable t) {
            log.error("oldDetailThreadLocal处理异常", t);
        }
        return "";
    }

    /**
     * 更新后数据
     */
    private String getNewDetail() {
        try {
            String newDetail = NEW_DETAIL_THREAD_LOCAL.get();
            if (StringUtils.isNotBlank(newDetail)) {
                NEW_DETAIL_THREAD_LOCAL.remove();
                return newDetail;
            }
        } catch (Throwable t) {
            log.error("newDetailThreadLocal处理异常", t);
        }
        return "";
    }

    /**
     * 更新后数据
     *
     * @return LogRequest
     */
    private LogRequest getLog() {
        try {
            LogRequest logRequest = LOG_THREAD_LOCAL.get();
            LOG_THREAD_LOCAL.remove();
            return logRequest;
        } catch (Throwable t) {
            log.error("logThreadLocal处理异常", t);
        }
        return null;
    }

    /**
     * 设置数据更新后的对象,用来记录日志
     *
     * @param newDetail 需要更新的信息对象
     */
    public static void setNewDetail(Object newDetail) {
        try {
            NEW_DETAIL_THREAD_LOCAL.set(JSON.toJSONString(newDetail));
        } catch (Exception e) {
            log.error("newDetailThreadLocal处理异常", e);
        }
    }

    /**
     * 设置数据更新前的对象,用来记录日志
     *
     * @param oldDetail 需要保存的原来信息对象
     */
    public static void setOldDetail(Object oldDetail) {
        try {
            OLD_DETAIL_THREAD_LOCAL.set(JSON.toJSONString(oldDetail));
        } catch (Exception e) {
            log.error("oldDetailThreadLocal处理异常", e);
        }
    }

    /**
     * 设置数据更新前的对象,用来记录日志
     *
     * @param logRequest 日志对象
     */
    public static void setLog(LogRequest logRequest) {
        try {
            LOG_THREAD_LOCAL.set(logRequest);
        } catch (Exception e) {
            LogAopAction.log.error("logThreadLocal处理异常", e);
        }
    }
}
